package com.scriptlib;

import android.graphics.Point;
import android.media.Image;
import android.util.Log;

import java.lang.reflect.InvocationTargetException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class AnchorGraphicHelper {
    private static final int _NORMAL = -1;
    private static final int _LEFT = 0;
    private static final int _CENTER = 1;
    private static final int _RIGHT = 2;
    private final AJFunc MyAJFunc;
    private byte[] ScreenData;
    private final double Multiple;
    private int Width, Height, RowStride;
    private final int Left, Top, Right, Bottom, Center, DevWidth, DevHeight;
    public short[][] RedList = new short[256][10240];
    private final int[] Steps = new int[256];
    private final int[] Lens = new int[256];

    /**
     * 构造函数
     *
     * @param obj:       aj_runtime
     * @param devWidth:  开发分辨率_宽
     * @param devHeight: 开发分辨率_高
     * @param left:      运行设备布局 startX
     * @param top:       运行设备布局 startY
     * @param right      运行设备布局 endX
     * @param bottom:    运行设备布局 endY
     */
    public AnchorGraphicHelper(Object obj, int devWidth, int devHeight, int left, int top, int right, int bottom) {
        this.MyAJFunc = new AJFunc(obj);
        this.DevWidth = devWidth;
        this.DevHeight = devHeight;
        this.Left = left;
        this.Top = top;
        this.Right = right;
        this.Bottom = bottom;
        this.Center = Round((this.Right - this.Left + 1.0) / 2) + Left - 1;
        this.Multiple = (this.Bottom - this.Top + 1.0) / devHeight;
        Init();
    }

    /**
     * 构造函数
     *
     * @param obj:         aj_runtime
     * @param description: 开发及运行参数
     */
    public AnchorGraphicHelper(Object obj, int[] description) {
        this(obj, description[0], description[1], description[2], description[3], description[4], description[5]);
    }

    private void Init() {
        Image image = null;
        try {
            image = (Image) MyAJFunc.CaptureMethod.invoke(MyAJFunc.ScreenCapturer);
        } catch (IllegalAccessException | InvocationTargetException e) {
            Log.e("ScriptLib", e.toString());
        }
        if (image == null) {
            return;
        }
        ByteBuffer byteBuf = image.getPlanes()[0].getBuffer();
        Width = image.getWidth();
        Height = image.getHeight();
        RowStride = image.getPlanes()[0].getRowStride();
        ScreenData = new byte[RowStride * Height];
        byteBuf.position(0);
        byteBuf.get(ScreenData, 0, RowStride * Height);
        Arrays.fill(Lens, 10240);
        GetRedList();
    }

    static public String Version() {
        return AJFunc.Version();
    }

    static public String GetInfo() {
        return AJFunc.GetInfo();
    }

    /**
     * 四舍六入五成双
     */
    public int Round(double num) {
        int local = (int) ((num - (int) num) * 100);
        if (local / 10 >= 6)
            return (int) num + 1;
        if (local / 10 <= 4)
            return (int) num;
        if (local % 10 == 0) {
            if ((int) num % 2 == 0) {
                return (int) num;
            }
        }
        return (int) num + 1;
    }

    /**
     * 比Math类库abs()方法性能更高的取绝对值的方法 from https://blog.csdn.net/qq_15071263/article/details/77142001
     */
    public int Abs(int num) {
        return (num ^ (num >> 31)) - (num >> 31);
    }

    /**
     * 暴露ScreenData
     */
    public byte[] GetScreenData() {
        return ScreenData;
    }

    /**
     * 尝试获取最新的image byte[]
     */
    public boolean KeepScreen(boolean sign) {
        if (GetLastFrame()) {
            if (sign) {
                GetRedList();
            }
            return true;
        } else {
            return false;
        }
    }

    private boolean GetLastFrame() {
        Image image = null;
        try {
            image = (Image) MyAJFunc.CaptureMethod.invoke(MyAJFunc.ScreenCapturer);
        } catch (IllegalAccessException | InvocationTargetException e) {
            Log.e("ScriptLib", e.toString());
        }
        if (image == null) {
            return false;
        }
        ByteBuffer byteBuf = image.getPlanes()[0].getBuffer();
        byteBuf.position(0);
        byteBuf.get(ScreenData, 0, RowStride * Height);
        return true;
    }

    /**
     * 获取所有r值对应的坐标,找色效率更快
     */
    public void GetRedList() {
        Arrays.fill(Steps, 0);
        int location;
        for (short i = 0; i < (short) Height; i++) {
            location = RowStride * i;
            for (short j = 0; j < (short) Width; j++) {
                int k = ScreenData[location] & 0xff;
                int step = Steps[k];
                if (step == Lens[k]) {
                    Lens[k] = Lens[k] + 10240;
                    RedList[k] = Arrays.copyOf(RedList[k], Lens[k]);
                }
                RedList[k][step] = j;
                RedList[k][step + 1] = i;
                location += 4;
                Steps[k] = step + 2;
            }
        }
    }

    /**
     * 获取与运行设备相关的坐标
     *
     * @param x:    开发分辨率的x
     * @param y:    开发分辨率的y
     * @param mode: 坐标对齐方式
     */
    public Point GetPoint(int x, int y, int mode) {
        Point result = new Point();
        if (mode == _LEFT || mode == _NORMAL) {
            result.x = Round(x * Multiple) + Left;
        } else if (mode == _CENTER) {
            result.x = Round(Center - (DevWidth / 2.0 - x - 1) * Multiple);
        } else if (mode == _RIGHT) {
            result.x = Round(Right - (DevWidth - x - 1) * Multiple);
        }
        result.y = Round(y * Multiple) + Top;
        return result;
    }

    /**
     * 获取指定像素数据_string
     */
    public String GetPixelStr(int x, int y, int mode) {
        int[] result = GetPixel(x, y, mode);
        int color = (result[0] & 0xff) << 16 | (result[1] & 0xff) << 8 | (result[2] & 0xff);
        return String.format("0x%06x", color);
    }

    public int GetPixelInt(int x, int y, int mode) {
        int[] result = GetPixel(x, y, mode);
        return (result[0] & 0xff) << 16 | (result[1] & 0xff) << 8 | (result[2] & 0xff);
    }

    public int[] GetPixel(int x, int y, int mode) {
        Point point = GetPoint(x, y, mode);
        if (point.x >= 0 && point.x < Width && point.y >= 0 && point.y < Height) {
            int location = point.x * 4 + point.y * RowStride;
            int[] result = new int[3];
            result[0] = ScreenData[location] & 0xff;
            result[1] = ScreenData[location + 1] & 0xff;
            result[2] = ScreenData[location + 2] & 0xff;
            return result;
        } else {
            return new int[]{0, 0, 0};
        }
    }

    public boolean CompareColor(int[] description, int sim, int offset) {
        int offsetSize = offset == 0 ? 1 : 9;
        int x = description[0];
        int y = description[1];
        int _x;
        int _y;

        int similarity = Round(255 - 255 * (sim / 100.0));
        int similarity_R = similarity + description[5];
        int similarity_G = similarity + description[6];
        int similarity_B = similarity + description[7];

        Point[] offsetPoint = new Point[]{
                new Point(x, y),
                new Point(x - 1, y - 1),
                new Point(x - 1, y),
                new Point(x - 1, y + 1),
                new Point(x, y - 1),
                new Point(x, y + 1),
                new Point(x + 1, y - 1),
                new Point(x + 1, y),
                new Point(x + 1, y + 1),
        };

        for (int i = 0; i < offsetSize; i++) {
            _x = offsetPoint[i].x;
            _y = offsetPoint[i].y;
            if (_x >= 0 && _x < Width && _y >= 0 && _y < Height) {
                int location = _x * 4 + _y * RowStride;
                if (Abs((ScreenData[location] & 0xff) - description[2]) <= similarity_R) {
                    if (Abs((ScreenData[location + 1] & 0xff) - description[3]) <= similarity_G) {
                        if (Abs((ScreenData[location + 2] & 0xff) - description[4]) <= similarity_B) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }

    private boolean CompareColor(int[] description, int offsetX, int offsetY, int offsetSize) {
        int x = description[0] + offsetX;
        int y = description[1] + offsetY;
        int _x;
        int _y;

        Point[] offsetPoint = new Point[]{
                new Point(x, y),
                new Point(x - 1, y - 1),
                new Point(x - 1, y),
                new Point(x - 1, y + 1),
                new Point(x, y - 1),
                new Point(x, y + 1),
                new Point(x + 1, y - 1),
                new Point(x + 1, y),
                new Point(x + 1, y + 1),
        };

        for (int i = 0; i < offsetSize; i++) {
            _x = offsetPoint[i].x;
            _y = offsetPoint[i].y;
            if (_x >= 0 && _x < Width && _y >= 0 && _y < Height) {
                int location = _x * 4 + _y * RowStride;
                if (Abs((ScreenData[location] & 0xff) - description[2]) <= description[5]) {
                    if (Abs((ScreenData[location + 1] & 0xff) - description[3]) <= description[6]) {
                        if (Abs((ScreenData[location + 2] & 0xff) - description[4]) <= description[7]) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }

    private boolean CompareColorEx(int[][] description, int x, int y, int offsetSize) {
        for (int[] i : description) {
            if (!CompareColor(i, x, y, offsetSize)) {
                return false;
            }
        }
        return true;
    }

    private Point FindMultiColor(int startX, int startY, int endX, int endY, int[] firstDescription, int[][] offsetDescription, int offsetSize) {
        int r = firstDescription[2];
        short[] list = RedList[r];
        int location;
        for (int i = 0, length = Steps[r] - 2; i < length; i += 2) {
            if (list[i] >= startX && list[i] <= endX && list[i + 1] >= startY && list[i + 1] <= endY) {
                location = list[i + 1] * RowStride + list[i] * 4;
                if (Abs((ScreenData[location + 1] & 0xff) - firstDescription[3]) <= firstDescription[6]) {
                    if (Abs((ScreenData[location + 2] & 0xff) - firstDescription[4]) <= firstDescription[7]) {
                        if (CompareColorEx(offsetDescription, list[i], list[i + 1], offsetSize)) {
                            return new Point(list[i], list[i + 1]);
                        }
                    }
                }
            } else if (list[i] > endX && list[i + 1] > endY) {
                return new Point(-1, -1);
            }
        }
        return new Point(-1, -1);
    }

    //<editor-fold desc="CompareColorEx">

    public boolean CompareColorEx(int[][] description, int sim, int offset) {
        for (int[] _description : description) {
            if (!CompareColor(_description, sim, offset)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 条件循环多点比色
     *
     * @param description: 色组描述
     * @param sim:         相似度
     * @param offset:      偏移查找
     * @param timeout:     超时时间
     * @param timelag:     间隔时间
     * @param sign:        跳出条件,0为比色成功时返回,1为比色失败时返回
     */
    public boolean CompareColorExLoop(int[][] description, int sim, int offset, long timeout, long timelag, int sign) throws InterruptedException {
        int num = (int) (timeout / timelag);
        if (sign == 0) {
            for (int i = 0; i < num; i++) {
                if (KeepScreen(false)) {
                    if (CompareColorEx(description, sim, offset)) {
                        return true;
                    }
                }
                Thread.sleep(timelag);
            }
        } else {
            for (int i = 0; i < num; i++) {
                if (KeepScreen(false)) {
                    if (!CompareColorEx(description, sim, offset)) {
                        return true;
                    }
                }
                Thread.sleep(timelag);
            }
        }
        return false;
    }

    public int[][] GetCmpColorArray(int devWidth, int devHeight, int[][] description) {
        double multiple = (Bottom - Top + 1.0) / devHeight;
        int[][] _description = new int[description.length][8];
        for (int i = 0; i < description.length; i++) {
            if (description[i][0] == _LEFT) {
                _description[i][0] = Round(description[i][1] * multiple) + Left;
            } else if (description[i][0] == _CENTER) {
                _description[i][0] = Round(Center - (devWidth / 2.0 - description[i][1] - 1) * multiple);
            } else if (description[i][0] == _RIGHT) {
                _description[i][0] = Round(Right - (devWidth - description[i][1] - 1) * multiple);
            }
            _description[i][1] = Round(description[i][2] * multiple) + Top;
            _description[i][2] = (description[i][3] & 0xff0000) >> 16;
            _description[i][3] = (description[i][3] & 0xff00) >> 8;
            _description[i][4] = description[i][3] & 0xff;
            _description[i][5] = _description[i][6] = _description[i][7] = 0;
            if (description[i].length == 5) {
                _description[i][5] = (description[i][4] & 0xff0000) >> 16;
                _description[i][6] = (description[i][4] & 0xff00) >> 8;
                _description[i][7] = description[i][4] & 0xff;
            }
        }
        return _description;
    }

// </editor-fold>
    //<editor-fold desc="FindMultiColor">

    /**
     * 多点找色
     *
     * @param startX:      查找区域的左上X坐标
     * @param startY:      查找区域的左上Y坐标
     * @param endX:        查找区域的右下Y坐标
     * @param endY:        查找区域的右下Y坐标
     * @param description: 色组描述
     * @param sim:         相似度
     * @param offset:      偏移查找
     */
    public Point FindMultiColor(int startX, int startY, int endX, int endY, int[][] description, int sim, int offset) {
        startX = Math.max(startX, 0);
        startY = Math.max(startY, 0);
        endX = endX < Width ? endX : Width - 1;
        endY = endY < Height ? endY : Height - 1;
        int[] firstDescription = Arrays.copyOf(description[0], 8);
        int[][] offsetDescription = new int[description.length - 1][8];

        int similarity = Round(255 - 255 * (sim / 100.0));
        firstDescription[5] = similarity + description[0][5];
        firstDescription[6] = similarity + description[0][6];
        firstDescription[7] = similarity + description[0][7];

        for (int j = 0; j < offsetDescription.length; j++) {
            offsetDescription[j] = Arrays.copyOf(description[j + 1], 8);
            offsetDescription[j][5] = similarity + description[j + 1][5];
            offsetDescription[j][6] = similarity + description[j + 1][6];
            offsetDescription[j][7] = similarity + description[j + 1][7];
        }

        int offsetSize = offset == 0 ? 1 : 9;
        boolean step = true;
        for (int i = 0; i <= firstDescription[5]; i++) {
            int num;
            if (step) {
                num = description[0][2] + i;
                if (i != 0) {
                    i--;
                    step = false;
                }
            } else {
                num = description[0][2] - i;
                step = true;
            }
            if (num < 256 && num > -1) {
                firstDescription[2] = num & 0xff;
                Point point = FindMultiColor(startX, startY, endX, endY, firstDescription, offsetDescription, offsetSize);
                if (point.x != -1) {
                    return point;
                }
            }

        }
        return new Point(-1, -1);
    }

    public Point FindMultiColor(int[] rect, int[][] description, int sim, int offset) {
        return FindMultiColor(rect[0], rect[1], rect[2], rect[3], description, sim, offset);
    }

    /**
     * 多点找色_返回所有符合的坐标
     */
    public List<Point> FindMultiColorEx(int startX, int startY, int endX, int endY, int[][] description, int sim, int offset) {
        List<Point> result = new ArrayList<>();
        startX = Math.max(startX, 0);
        startY = Math.max(startY, 0);
        endX = endX < Width ? endX : Width - 1;
        endY = endY < Height ? endY : Height - 1;
        int[] firstDescription = Arrays.copyOf(description[0], 8);
        int[][] offsetDescription = new int[description.length - 1][8];

        int similarity = Round(255 - 255 * (sim / 100.0));
        firstDescription[5] = similarity + description[0][5];
        firstDescription[6] = similarity + description[0][6];
        firstDescription[7] = similarity + description[0][7];

        for (int j = 0; j < offsetDescription.length; j++) {
            offsetDescription[j] = Arrays.copyOf(description[j + 1], 8);
            offsetDescription[j][5] = similarity + description[j + 1][5];
            offsetDescription[j][6] = similarity + description[j + 1][6];
            offsetDescription[j][7] = similarity + description[j + 1][7];
        }

        int offsetSize = offset == 0 ? 1 : 9;
        boolean step = true;
        for (int i = 0; i <= firstDescription[5]; i++) {
            int num;
            if (step) {
                num = description[0][2] + i;
                if (i != 0) {
                    i--;
                    step = false;
                }
            } else {
                num = description[0][2] - i;
                step = true;
            }
            if (num < 256 && num > -1) {
                firstDescription[2] = num & 0xff;
                Point point = FindMultiColor(startX, startY, endX, endY, firstDescription, offsetDescription, offsetSize);
                if (point.x != -1) {
                    result.add(point);
                }
            }
        }
        return result;
    }

    /**
     * 条件循环多点找色
     *
     * @param description: 色组描述
     * @param sim:         相似度
     * @param offset:      偏移查找
     * @param timeout:     超时时间
     * @param timelag:     间隔时间
     * @param sign:        跳出条件,0为找色成功时返回,1为找色失败时返回
     */
    public Point FindMultiColorLoop(int startX, int startY, int endX, int endY, int[][] description, int sim, int offset, long timeout, long timelag, int sign) throws InterruptedException {
        int num = (int) (timeout / timelag);
        if (sign == 0) {
            for (int i = 0; i < num; i++) {
                if (KeepScreen(true)) {
                    Point result = FindMultiColor(startX, startY, endX, endY, description, sim, offset);
                    if (result.x != -1) {
                        return result;
                    }
                }
                Thread.sleep(timelag);
            }
        } else {
            for (int i = 0; i < num; i++) {
                if (KeepScreen(true)) {
                    Point result = FindMultiColor(startX, startY, endX, endY, description, sim, offset);
                    if (result.x == -1) {
                        return result;
                    }
                }
                Thread.sleep(timelag);
            }
        }
        return FindMultiColor(startX, startY, endX, endY, description, sim, offset);
    }

    public int[][] GetFindColorArray(int devWidth, int devHeight, int[][] description) {
        int[][] _description = new int[description.length][8];
        double multiple = (Bottom - Top + 1.0) / devHeight;
        if (description[0][0] == _LEFT || description[0][0] == _NORMAL) {
            _description[0][0] = Round(description[0][1] * multiple) + Left;
        } else if (description[0][0] == _CENTER) {
            _description[0][0] = Round(Center - (devWidth / 2.0 - description[0][1] - 1) * multiple);
        } else if (description[0][0] == _RIGHT) {
            _description[0][0] = Round(Right - (devWidth - description[0][1] - 1) * multiple);
        }
        _description[0][1] = Round(description[0][2] * multiple);
        _description[0][2] = (description[0][3] & 0xff0000) >> 16;
        _description[0][3] = (description[0][3] & 0xff00) >> 8;
        _description[0][4] = description[0][3] & 0xff;
        _description[0][5] = _description[0][6] = _description[0][7] = 0;
        if (description[0].length == 5) {
            _description[0][5] = (description[0][4] & 0xff0000) >> 16;
            _description[0][6] = (description[0][4] & 0xff00) >> 8;
            _description[0][7] = description[0][4] & 0xff;
        }
        for (int i = 1; i < description.length; i++) {
            if (description[i][0] == _LEFT || description[i][0] == _NORMAL) {
                _description[i][0] = Round(description[i][1] * multiple) + Left - _description[0][0];
            } else if (description[i][0] == _CENTER) {
                _description[i][0] = Round(Center - (devWidth / 2.0 - description[i][1] - 1) * multiple) - _description[0][0];
            } else if (description[i][0] == _RIGHT) {
                _description[i][0] = Round(Right - (devWidth - description[i][1] - 1) * multiple) - _description[0][0];
            }
            _description[i][1] = Round(description[i][2] * multiple) + Top - _description[0][1];
            _description[i][2] = (description[i][3] & 0xff0000) >> 16;
            _description[i][3] = (description[i][3] & 0xff00) >> 8;
            _description[i][4] = description[i][3] & 0xff;
            _description[i][5] = _description[i][6] = _description[i][7] = 0;
            if (description[i].length == 5) {
                _description[i][5] = (description[i][4] & 0xff0000) >> 16;
                _description[i][6] = (description[i][4] & 0xff00) >> 8;
                _description[i][7] = description[i][4] & 0xff;
            }
        }
        return _description;
    }

// </editor-fold>
}
